﻿using System;
using System.Collections.Generic;
using System.Text;
using Axiom;
using Axiom.Core;
using Axiom.Graphics;
using System.IO;
using Axiom.Math;
using Axiom.Input;
using Axiom.SceneManagers.Octree;

namespace BaseGame
{
    class BaseGameCore : IDisposable
    {

        const float movementSpeed = 50;
        const int BonusPackCount = 20;
        const int WorldSize = 1024;

        Root engine = null;
        RenderWindow _renderWindow;
        TerrainSceneManager _sceneManager;
        Camera camera;

        InputReader input;
        Vector3 velocity = Vector3.Zero;

        SceneNode PlayerNode;
        List<Vector3> BonusPackPositions;

        Random Randomizer;

        #region Axiom Setup
        public void Initialize()
        {
            Randomizer = new Random(DateTime.Now.Millisecond);

            CreateRoot();
            DefineResources();
            SetupRenderSystem();
            CreateRenderWindow();
            InitializeResourceGroups();
            CreateScene();

            StartRenderLoop();
        }

        void CreateRoot()
        {
            engine = new Root("AxiomEngine.log");
        }

        void DefineResources()
        {
            ResourceGroupManager.Instance.AddResourceLocation("GpuPrograms", "Folder", "General");

            ResourceGroupManager.Instance.AddResourceLocation("Resources/Maps/Level1", "Folder", "Maps");
            ResourceGroupManager.Instance.AddResourceLocation("Resources/Characters/Robot", "Folder", "Characters");
            ResourceGroupManager.Instance.AddResourceLocation("Resources/Characters/Mouse", "Folder", "Characters");
            ResourceGroupManager.Instance.AddResourceLocation("Resources/Objects/Cookie", "Folder", "Characters");
        }

        void SetupRenderSystem()
        {
            RenderSystem rs = engine.RenderSystems["DirectX9"];
            rs.SetConfigOption("Full Screen", "No");
            rs.SetConfigOption("Video Mode", "800 x 600 @ 32-bit colour"); // @ 32-bit colour
            engine.RenderSystem = rs;
        }

        void CreateRenderWindow()
        {
            _renderWindow = engine.Initialize(true, "AxiomDemoWindow");
        }

        void InitializeResourceGroups()
        {
            TextureManager.Instance.DefaultMipmapCount = 5;
            ResourceGroupManager.Instance.InitializeAllResourceGroups();
        }

        private void InsertEntities()
        {
            //initialize palyer position
            //int WorldSize = _sceneManager.options.worldSize;
            Vector3 PlayerPosition = new Vector3(50, 0, 2); // Vector3.Zero;
            PlayerPosition.y = _sceneManager.GetHeightAt(PlayerPosition, 0);

            //put player in place
            Entity ent = _sceneManager.CreateEntity("Player", "mouse.mesh");
            ent.CastShadows = true;
            PlayerNode = _sceneManager.RootSceneNode.CreateChildSceneNode("PlayerNode", PlayerPosition);
            PlayerNode.AttachObject(ent);
            PlayerNode.Scale = new Vector3(5f, 5f, 5f);

            //put goals in place 
            BonusPackPositions = new List<Vector3>(BonusPackCount);
            for (int i = 0; i < BonusPackCount; i++)
            {
                Vector3 pos = new Vector3(Randomizer.Next(WorldSize), 0, Randomizer.Next(WorldSize));
                pos.y = _sceneManager.GetHeightAt(pos, 0);
                BonusPackPositions.Add(pos);
                ent = _sceneManager.CreateEntity("Cookie" + i.ToString(), "cookie.mesh");
                ent.CastShadows = true;
                SceneNode node = _sceneManager.RootSceneNode.CreateChildSceneNode("CookieNode" + i.ToString(), pos);
                node.AttachObject(ent);
                node.Scale = new Vector3(2f, 2f, 2f);
            }
        }

        private void CreateScene()
        {
            _sceneManager = (TerrainSceneManager)engine.CreateSceneManager("TerrainSceneManager");
            camera = _sceneManager.CreateCamera("Camera");
            Viewport vp = _renderWindow.AddViewport(camera);
            camera.AspectRatio = (float)(vp.ActualWidth / vp.ActualHeight);

            _sceneManager.LoadWorldGeometry("Terrain.xml");
            //_sceneManager.AmbientLight = new ColorEx(0f, 0f, 0f);

            InsertEntities();

            camera.Position = new Vector3(100f, 300f, 200f); //calcCameraPosition(PlayerPosition , 600f);
            camera.Pitch(-45f);
            camera.Near = 5f;
        }

        void StartRenderLoop()
        {
            input = PlatformManager.Instance.CreateInputReader();
            input.Initialize(_renderWindow, true, true, false, false);

            engine.FrameStarted += frameStart;
            engine.StartRendering();
        }
        #endregion

        /// <summary>
        /// General place for functions needed to be run every frame
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void frameStart(object sender, FrameEventArgs e)
        {
            checkInput(e);
        }

        /// <summary>
        /// Checks for input, moves camera and rotates it
        /// </summary>
        /// <param name="e"></param>
        void checkInput(FrameEventArgs e)
        {
            input.Capture();

            MoveCamera(e);

            if (input.IsMousePressed(MouseButtons.Left))
            {
                RaySceneQuery SceneQuery = _sceneManager.CreateRayQuery(
                    camera.GetCameraToViewportRay(
                        input.AbsoluteMouseX / (float)_renderWindow.Width,
                        input.AbsoluteMouseY / (float)_renderWindow.Height)
                        ,
                        0x40000000
                        );

                foreach (RaySceneQueryResultEntry result in SceneQuery.Execute())
                {
                    if (result.SceneObject != null)
                        result.SceneObject.ShowBoundingBox = true;
                    else if (result.worldFragment != null)
                        PlayerNode.Position = result.worldFragment.SingleIntersection;
                }
            }

            if (input.IsKeyPressed(KeyCodes.Escape))
                Root.Instance.QueueEndRendering();

        }

        private void MoveCamera(FrameEventArgs e)
        {

            //create new translation
            Vector3 translate = Vector3.Zero;
            if (input.IsKeyPressed(KeyCodes.W)) translate.z -= movementSpeed;
            if (input.IsKeyPressed(KeyCodes.A)) translate.x -= movementSpeed;
            if (input.IsKeyPressed(KeyCodes.S)) translate.z += movementSpeed;
            if (input.IsKeyPressed(KeyCodes.D)) translate.x += movementSpeed;

#if DEBUG
            if (input.IsKeyPressed(KeyCodes.Y))
                translate.y += input.IsKeyPressed(KeyCodes.LeftShift) ? -movementSpeed : movementSpeed;
#endif

            velocity += translate;
            float yOffset = _sceneManager.GetHeightAt(camera.Position, 0);
            camera.Move(velocity * e.TimeSinceLastFrame);

            Vector3 cameraPosCorrector = camera.Position;
            if (cameraPosCorrector.x < 50)
            {
                cameraPosCorrector.x = 50;
                velocity.x = 0;
            }
            else if (cameraPosCorrector.x >= WorldSize - 50)
            {
                cameraPosCorrector.x = WorldSize - 50;
                velocity.x = 0;
            }

            if (cameraPosCorrector.z < 50)
            {
                cameraPosCorrector.z = 50;
                velocity.z = 0;
            }
            else if (cameraPosCorrector.z >= WorldSize)
            {
                cameraPosCorrector.z = WorldSize;
                velocity.z = 0;
            }

            cameraPosCorrector.y += _sceneManager.GetHeightAt(cameraPosCorrector, 0) - yOffset;

            camera.Position = cameraPosCorrector;

            //camera.Yaw(-input.RelativeMouseX * .13f);
            //camera.Pitch(-input.RelativeMouseY * .13f);

            //slow down if not moving
            if (translate == Vector3.Zero)
                velocity *= (1 - (3 * e.TimeSinceLastFrame));
        }

        /// <summary>
        /// Make a position for camera to look at Obj from distance
        /// </summary>
        /// <param name="Obj"></param>
        /// <param name="distance"></param>
        /// <returns></returns>
        Vector3 calcCameraPosition(Vector3 ObjPostion, float distance)
        {
            //look at CameraMovement.nb for equations

            Vector3 ret = Vector3.Zero;
            ret.z = (float)Math.Sqrt(640 * Math.Sqrt(22000 + 4 * Math.Pow(distance, 2)));
            ret.y = (float)Math.Sqrt(22000 + 4 * Math.Pow(distance, 2));
            //ret.z = ret.y = distance / 1.414f;

            return ret + ObjPostion;
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (engine != null)
            {
                Root.Instance.RenderSystem.DetachRenderTarget(_renderWindow);
                engine.Dispose();
            }
            if (_sceneManager != null)
                _sceneManager.RemoveAllCameras();

            camera = null;
            _renderWindow.Dispose();
        }

        #endregion

    }
}
